using Game.Networking.Events;
using SMGCore.EventSys;
using System.Collections;
using System.Collections.Generic;

namespace Game.Networking.Server {
	public sealed class ServerScriptController : ServerSideController<ServerScriptController> {
		public const string StateFileName = "computers.info";

		Dictionary<uint, ComputingUnit> _computingUnits = new Dictionary<uint, ComputingUnit>();
		ScriptControllerData _state;

		GlobalData _globalObject = new GlobalData();

		Dictionary<string, UnitEventsWatcher> _watchers = new Dictionary<string, UnitEventsWatcher>();

		public ServerScriptController(ServerGameManager owner) : base(owner) {
		}

		public override void Init() {
			base.Init();
			_state = new ScriptControllerData();
			EventManager.Subscribe<OnClientDisconnected>(this, OnClientDisconnected);
		}

		public override void Load() {
			base.Load();
			var loaded = ServerSaveLoadController.Instance.LoadSaveFile<ScriptControllerData>(StateFileName);
			if ( loaded != null ) {
				_state = loaded;
			}
			foreach ( var item in _state.ComputerStates ) {
				CreateFromState( item );
			}
		}

		public override void PostLoad() {
			base.PostLoad();
			//TODO: update global data values
		}

		public override void Save() {
			base.Save();
			var allCompsList = new List<ComputingUnitData>(_computingUnits.Count);
			foreach ( var item in _computingUnits ) {
				if ( item.Value == null ) {
					continue;
				}
				var save = item.Value.Save();
				allCompsList.Add(save);
			}
			_state.ComputerStates = allCompsList;
			ServerSaveLoadController.Instance.SaveFileToDisk(StateFileName, _state);
		}

		public override void Update() {
			base.Update();
			_globalObject.Time = ServerGameManager.Time;
			//TODO: total tick time limit
			foreach ( var kv in _computingUnits ) {
				UpdateUnit(kv.Value);
			}
		}

		public override void Reset() {
			base.Reset();
			EventManager.Unsubscribe<OnClientDisconnected>(OnClientDisconnected);
		}

		public ComputingUnit GetUnitById(uint unitId) {
			if ( _computingUnits.TryGetValue(unitId, out var result) ) {
				return result;
			}
			return null;
		}

		public void DestroyUnit(uint unitId) {
			var unit = GetUnitById(unitId);
			if ( unit == null ) {
				return;
			}
			var ac = ServerAgentsController.Instance;
			ac.DestroyAgent(unitId);
			_computingUnits.Remove(unitId);
		}

		void UpdateUnit(ComputingUnit unit) {
			//TODO: update watchdog
			var ac = ServerAgentsController.Instance;

			if ( unit == null ) {
				return;
			}
			switch ( unit.State ) {
				case ComputingUnitState.Uninitialized:
					unit.Init();
					break;
				case ComputingUnitState.DomainCreated:
					unit.Compile();
					break;
				case ComputingUnitState.Compilation:
					if ( unit.CheckCompileResult() ) {
						unit.FinalizeCompilation();
					}
					break;
				case ComputingUnitState.Ready:
					if ( !ac.IsAgentEntityAlive(unit.ID) ) {
						break;
					}
					var agentEntity = ac.GetAgentEntity(unit.ID);
					unit.SetupProgram(_globalObject, agentEntity.Api, agentEntity.ID);
					break;
				case ComputingUnitState.Active:
					if ( !ac.IsAgentEntityAlive(unit.ID) ) {
						unit.ProcessProgramShutdown();
						unit.ClearRuntimeData();
						unit.State = ComputingUnitState.NoTargetEntity;
						break;
					}
					unit.RunUpdate();
                    break;
				case ComputingUnitState.RecompileRequest:
					unit.ProcessProgramShutdown();
					unit.ClearRuntimeData();
					unit.State = ComputingUnitState.Uninitialized;
					break;
				case ComputingUnitState.RecompileRequestNoSave:
					unit.ClearRuntimeData();
					unit.State = ComputingUnitState.Uninitialized;
					break;
				case ComputingUnitState.NoTargetEntity:
				case ComputingUnitState.BudgetExceeded:
				case ComputingUnitState.Paused:
				case ComputingUnitState.CompileError:
				case ComputingUnitState.RuntimeError:
				default:
					break;
			}
		}

		uint GetNewId() {
			_state.InstanceIndex++;
			return _state.InstanceIndex;
		}

		public ComputingUnit CreateNewUnit(string ownerUser) {
			var id = GetNewId();
			var unit = new ComputingUnit();
			unit.Create(id, ownerUser);
			unit.Init();
			_computingUnits.Add(id, unit);
			return unit;
		}

		public void RequestRecompile(uint unitId) {
			var unit = GetUnitById(unitId);
			if ( unit == null ) {
				return;
			}
			if ( unit.State != ComputingUnitState.Active ) {
				unit.State = ComputingUnitState.RecompileRequestNoSave;
			} else {
				unit.State = ComputingUnitState.RecompileRequest;
			}			
		}

		public void OnPlayerConnected(ClientState client) {
			if ( client == null ) {
				return;
			}
			var unitId = client.UnitId;
			ComputingUnit unit;
			if ( unitId == 0 ) {
				unit = CreateNewUnit(client.UserName);
				client.UnitId = unit.ID;
				unitId = unit.ID;
			} else {
				unit = GetUnitById(unitId);
			}
			if ( unit == null ) {
				unit = CreateNewUnit(client.UserName);
			}
			var ac = ServerAgentsController.Instance;
			var entity = ac.GetAgentEntity(unit.ID);
			if ( entity == null ) {
				entity = (ShipCore) ac.SpawnAgent(client.UserName, unitId, client.Team);
			}

			SendProgramFileListToUser(client);
			SendProgramFileToUser(client, unit.Data.DefaultProgramFile);
			unit.TextTerminal.SendLastMessagesToClient(10);

			if ( _watchers.TryGetValue(client.UserName, out var oldWatcher) ) {
				oldWatcher?.DeInit();
				_watchers.Remove(client.UserName);
			}
			var newWatcher = new NetworkUnitEventsWatcher();
			newWatcher.Setup(unit);
			_watchers.Add(client.UserName, newWatcher);
			newWatcher.OnAttachedEntityIdChanged(entity.ID);
		}

		/// <summary>
		/// Создает новый юнит и спавнит агента к нему. Для дебажных нужд
		/// </summary>
		public ComputingUnit CreateNewUnitAgentPair(string userName, TeamType team) {
			var unit = CreateNewUnit(userName);
			var ac = ServerAgentsController.Instance;
			var entity = ac.GetAgentEntity(unit.ID);
			if ( entity == null ) {
				ac.SpawnAgent(userName, unit.ID,  team);
			}
			return unit;
		}

		public void SendProgramFileListToUser(ClientState user) {
			if ( user == null || user.UnitId == 0 ) {
				return;
			}
			var unit = GetUnitById(user.UnitId);
			if ( unit == null ) {
				return;
			}
			var programList = unit.GetSourceCodeFileNames();
			ServerController.Instance.SendNetMessage(user, ServerPacketID.ProgramFileListSend, new S_ProgramListMessage() { ProgramList = programList });
		}

		public void SendProgramFileToUser(ClientState user, string fileName) {
			if ( user == null || user.UnitId == 0 ) {
				return;
			}
			var unit = GetUnitById(user.UnitId);
			if ( unit == null ) {
				return;
			}
			var program = unit.GetSourceCodeFile(fileName);
			ServerController.Instance.SendNetMessage(user, ServerPacketID.ProgramFileDownload, new S_ProgramSourceMessage() { ProgramName = fileName, ProgramCode = program }); 
		}

		public void SendTerminalMessageToUserUnit(ClientState user, string message) {
			if ( user == null ) {
				return;
			}
			var userUnit = GetUnitById(user.UnitId);
			if ( userUnit == null ) {
				return;
			}
			userUnit.ProcessConsoleMessage(message);
		}

		void CreateFromState(ComputingUnitData data) {
			if ( data == null ) {
				return;
			}
			if ( _computingUnits.ContainsKey(data.InstanceId) ) {
				UnityEngine.Debug.LogWarning($"ServerScriptController.CreateFromState: unit with id {data.InstanceId} is already created. Use another unit id");
			}
			var unit = new ComputingUnit();
			unit.Create(data.InstanceId, data.OwnerUser);
			unit.Data = data;
			unit.Init();
			_computingUnits.Add(unit.Data.InstanceId, unit);
		}

		void OnClientDisconnected(OnClientDisconnected e) {
			var client = e.State;
			if ( client == null ) {
				return;
			}
			if ( _watchers.TryGetValue(client.UserName, out var watcher) ) {
				watcher.DeInit();
				_watchers.Remove(client.UserName);
			}
		}
	}
}
